// Model binder for use with ASL widgets
// Copyright 2005 (C) Ralph Thomas

#include <model/model.h>
#include <model/receiver.h>
#include <model/attributeNames.h>
#include <model/attributeInfo.h>
#include <model/proxy/binder.h>
#include <binder.hpp>
#include <iostream>

namespace model {
	const adobe::static_name_t	key_url( "url" );
	const adobe::static_name_t	key_attr( "attribute" );
	const adobe::static_name_t	key_action( "action" );
	const adobe::static_name_t	key_param( "param" );
	
	class model_binder : public adobe::binder_t, receiver {
		adobe::sheet_t::monitor_value_t		_valueMonitor;
		adobe::sheet_t::monitor_active_t	_activeMonitor;
		adobe::binder_helper_t			_urlBinder;
		adobe::binder_helper_t			_attrBinder;
		model*					_model;
		std::string				_attr;
		std::string				_action;
		adobe::value_t				_param;
		//
		// These two are only required for making copies.
		//
		adobe::sheet_t&				_sheet;
		adobe::dictionary_t			_params;
		void onUrlChange( const adobe::value_t& newUrl ) {
			std::string url;
			//
			// If this fails then we just end up with an empty URL,
			// which means that the model gets destroyed and a new
			// one doesn't get created (which is what we want).
			//
		       	newUrl.get( url );
			//
			// Release the current model.
			//
			if( _model ) {
				_model->unsubscribe( this );
				_model->release();
			}
			_model = model::fromString( url );
			if( _model ) _model->subscribe( this );
			call_monitors();
		}
		void onAttrChange( const adobe::value_t& newAttr ) {
			newAttr.get( _attr );
			call_monitors();
		}
		void notify( const model* m, const std::string& attr ) {
			//
			// We only care if:
			//  - It's our model.
			//  - It's our attribute, or something we should look out for like
			//    kModelValid.
			//
			if( m != _model ) return;
			if( attr == kModelValid ) {
				if( !_model ) return;
				_model->unsubscribe( this );
				_model->release();
				_model = 0;
				call_monitors();
				return;
			}
			if( attr == _attr ) call_monitors();
		}
		void call_monitors() {
			//
			// If we have no model then we call the monitors with the news that
			// we have nothing, otherwise we get the information from the model.
			//
			if( !_model ) {
				if( _activeMonitor ) _activeMonitor( false );
				if( _valueMonitor ) _valueMonitor( adobe::value_t() );
				return;
			}
			//
			// Currently all actions are valid.
			//
			if( !_action.empty() ) {
				if( _activeMonitor ) _activeMonitor( true );
				return;
			}
			//
			// Get the model attributes to determine if our attribute exists and
			// if it is active or not.
			//
			if( _activeMonitor ) {
				attributeInfo ai;
				if( !_model->getAttribute( kModelAttributes ).get( ai ) ) {
					_activeMonitor( false );
				} else {
					//
					// Find our attribute in this hard-to-use interface.
					//
					bool found = false;
					for( unsigned int i = 0; i != ai.getNumberAttributes(); i++ ) {
						if( _attr != ai.getName( i ) ) continue;
						//
						// We found it!
						//
						found = true;
						_activeMonitor( ai.getSettable( i ) );
						break;
					}
					//
					// If we didn't find our attribute then assume that it's
					// writable.
					//
					if( !found ) _activeMonitor( true );
				}
			}
			//
			// Now that we've returned the active state of our attribute, let's
			// return the value.
			//
			if( _valueMonitor ) _valueMonitor( _model->getAttribute( _attr ) );
		}
	  public:
		model_binder( const adobe::dictionary_t& params, adobe::sheet_t& s )
		: _urlBinder(s), _attrBinder(s), _model(0), _sheet( s ),
		  _params( params ) {
			//
			// Verify that we have all of the information we need.
			//
			if( !params.count( key_url ) )
				throw std::runtime_error( "model binder: no url "
					"parameter supplied!" );
			if( !params.count( key_attr ) &&
			    !params.count( key_action ) )
				throw std::runtime_error( "model binder: no attribute "
					"or action parameter supplied!" );
			//
			// These are our value binders. Currently we don't bother
			// monitoring the active state of either inputs because they
			// don't change anything that we report.
			//
			adobe::sheet_t::monitor_value_t urlMonitor =
				boost::bind( &model_binder::onUrlChange, this, _1 );
			adobe::sheet_t::monitor_value_t attrMonitor =
				boost::bind( &model_binder::onAttrChange, this, _1 );
			//
			// Extract the action and parameter out of the dictionary;
			// this information is only used by invoke.
			//
			params.get<std::string>( key_action, _action );
			params.get<adobe::value_t>( key_param, _param );
			//
			// Extract out the bits to bind to from the dictionary.
			//
			_urlBinder.initialize( params.at( key_url ), urlMonitor, 0 );
			if( params.count( key_attr ) )
				_attrBinder.initialize( params.at( key_attr ), attrMonitor, 0);
		}
		virtual ~model_binder() {
			if( _model ) {
				_model->unsubscribe( this );
				_model->release();
				_model = 0;
			}
		}
		void set_signals( const adobe::sheet_t::monitor_value_t& valuemon,
			const adobe::sheet_t::monitor_active_t& activemon ) {
			_valueMonitor = valuemon;
			_activeMonitor = activemon;
			//
			// Simulate a change to call those functions.
			//
			if( _model && !_attr.empty() ) notify( _model, _attr );
		}
		void substitute( const adobe::name_t& name,
			const adobe::value_t& val ) {
			//
			// Substitute in our binders, we don't look after this
			// stuff.
			//
			_urlBinder.substitute( name, val );
			_attrBinder.substitute( name, val );
		}
		adobe::binder_ptr_t copy() const {
			return adobe::binder_ptr_t( new model_binder( _params,
				_sheet ) );
		}
		void set( const adobe::value_t& val ) {
			if( _model && !_attr.empty() )
				_model->setAttribute( _attr, val );
		}
		void invoke() {
			if( !_model ) return;
			if( _action.empty() ) return;
			_model->execute( _action, _param );
		}
	};
	adobe::value_t make_model_binder( const adobe::dictionary_t& parameters,
		adobe::sheet_t& sheet ) {
		model_binder* b( 0 );
		try {
			b = new model_binder( parameters, sheet );
			return adobe::value_t( adobe::binder_ptr_t(b) );
		} catch( ... ) {
			if( b ) delete b;
			throw;
		}
		return adobe::value_t();
	}
};

